// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`timescale 1ns/1ns
`default_nettype none
import i3c_pkg::*;
import i3c_error_codes_pkg::*;


module master_fsm #(
    parameter PEC_ENABLE_PARAM = 1  // this parameter enables PEC byte support for I3C transmit and receive transfer (for MCTP I3C Binding protocol use-case) 
) (

    input wire clk,
    input wire reset_n,

    // signals from the reg file
    output logic fifo_write,

    input  logic            bus_enable,
    input  logic            resume,
    input  logic            abort,
    output logic [31:0]     ibi,
    output logic            ibi_message_ready,
    input  logic            ibi_message_clear,
    output logic            command_fifo_read_req,
    input  logic [31:0]     command_fifo_read_data,
    input  logic            command_fifo_empty,
    input  logic            command_fifo_full,
    output logic            clr_read_fifo_dyn_addr_par_pulse,

    output logic            response_fifo_write_req,
    output logic [31:0]     response_fifo_write_data,
    input  logic            response_fifo_empty,
    input  logic            response_fifo_full,

    output logic            read_fifo_write_req,
    output logic [31:0]     read_fifo_write_data,
    input  logic            read_fifo_empty,
    input  logic            read_fifo_full,

    output logic            write_fifo_read_req,
    input  logic [31:0]     write_fifo_read_data,
    input  logic            write_fifo_empty,
    input  logic            write_fifo_full,


    // signals to the PHY
    //input logic new_status,
    input  logic            busy,
    input  status_t         status,
    output request_t        request,
    output logic [7:0]      wr_data,
    input  logic [7:0]      rd_data,
    input  logic            rd_data_valid,
    
    output logic [7:0]      link_speed,
    output logic [2:0]      i3c_speed

);

localparam ENTDAA_CODE = 8'h07;

localparam RESERVE_BYTE = 7'h7E;
typedef enum logic [5:0] {
        IDLE, 
        START, 
        WRITE_RESERVE_BYTE, 
        ACK_AFTER_RESERVE_BYTE,
        WRITE_ADDRESS, 
        START_DYNAMIC_ADDR, 
        ACK_HANDOFF, 
        ACK_NOHANDOFF, 
        READ, 
        CONTINUE_READ, 
        WRITE,
        CONTINUE_WRITE,
        WRITE_PEC,
        CONTINUE_WRITE_PEC,
        SET_STOP,
        REPEAT_START_DYNAMIC_ADDR,
        WRITE_RESERVE_BYTE_DYNAMIC_ADDR,
        ACK_AFTER_RESERVE_BYTE_DYNAMIC_ADDR,
        READ_UNIQUE_ID,
        WRITE_DYNAMIC_ADDR,
        WRITE_PARITY_DYNAMIC_ADDR,
        FLUSH_CMD_FIFO, 
        WRITE_CCC,
        PUSH_STATUS,
        IBI_READ_ADDR,
        IBI_SEND_ACK,
        IBI_SEND_NACK,
        IBI_READ_BYTE,
        IBI_READ_CONTINUE
        } state_t;

status_t status_ff;
logic new_status;
state_t state, state_next;
logic [2:0] unique_id_read_counter, unique_id_counter_next;
error_code_t status_reg;
error_code_t status_reg_next;
logic [2:0] command_read_counter;
logic clear_command_read_counter;
logic command_ready;

logic [2:0] data_read_counter;
logic clear_data_read_counter;
logic data_ready;
logic [31:0] data_tx_reg;
logic [31:0] data_tx_reg_next;
logic [31:0] data_rx_reg;
logic [31:0] data_rx_reg_next;

logic [6:0] address;
logic [8:0] num_bytes;
logic toc, rnw;
logic [2:0] command_mode;
logic [8:0] expected_num_bytes, expected_num_bytes_next;


logic [6:0] address_reg, address_reg_next;
logic [8:0] num_bytes_reg, num_bytes_reg_next;
logic toc_reg, toc_reg_next;
logic rnw_reg, rnw_reg_next;


logic [1:0] write_shift_counter, write_shift_counter_next;
logic [1:0] read_shift_counter, read_shift_counter_next;
logic [31:0] ibi_reg, ibi_reg_next;
logic ibi_message_ready_next;
logic [6:0] dyanmic_addr_assignemnt_reg, dyanmic_addr_assignemnt_reg_next;

logic [7:0] pec_code;
logic       pec_enable_int;
logic       pec_enable;

logic [1:0] dynamic_addr_nack_counter, dynamic_addr_nack_counter_next;

logic clr_read_fifo_dyn_addr_par;
logic clr_read_fifo_dyn_addr_par_dly;
logic rd_data_valid_dly;
logic par_bit;


assign ibi = ibi_reg;

// command decode
// extract relevant fields of the command here
assign toc = command_fifo_read_data[31];
assign rnw = command_fifo_read_data[29];
assign i3c_speed = command_fifo_read_data[28:26]; 
assign num_bytes = command_fifo_read_data[20:12]; 
assign address = command_fifo_read_data[10:4];
assign pec_enable = command_fifo_read_data[3];
assign command_mode = command_fifo_read_data[2:0]; 

localparam STANDARD_TRANSFER_MODE = 3'h0;
localparam ADDR_ASSIGNMENT_MODE = 3'h2;


generate
if (PEC_ENABLE_PARAM) begin
    
    logic       flush_crc;
    logic [7:0] crc_data_in;
    logic       update_crc;

    assign flush_crc = (state == START);
    assign crc_data_in = (state == WRITE || state == WRITE_ADDRESS) ? wr_data : rd_data;
    assign update_crc = new_status && (((status_ff == STATUS_DATA) && (state == WRITE)) || ((status_ff == STATUS_ADDRESS) && (state == WRITE_ADDRESS)) || ((status_ff == STATUS_DATA_LAST) && (state == READ)));
    assign pec_enable_int = pec_enable;

    // MCTP PEC using CRC8
    altr_i2c_crc8_d8 u_mctp_pec (
        .clk            (clk),
        .reset_n        (reset_n),
        .flush_crc      (flush_crc),
        .update_crc     (update_crc),
        .crc_data_in    (crc_data_in),
        .crc_code_out   (pec_code)

    );

end
else begin

    assign pec_enable_int = 1'b0;
    assign pec_code = 8'd0;
    
end
endgenerate


always_comb begin
    case (i3c_speed)
        3'd0 : link_speed = 8'd1;
        3'd1 : link_speed = 8'd3;
        3'd2 : link_speed = 8'd7;
        3'd3 : link_speed = 8'd15;
        3'd4 : link_speed = 8'd31;
        3'd5 : link_speed = 8'd63;
        3'd6 : link_speed = 8'd127;
        3'd7 : link_speed = 8'd255;
        default : link_speed = 8'd1;
    endcase
end

assign command_ready = & command_read_counter;

// counter counts up to 7 because 7 is the the worst case read latency for the 4 way fifo
always_ff @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        command_read_counter <= 3'h0;
    end 
    else begin
        if (command_read_counter > 0 && command_read_counter < 3'h7)
            command_read_counter <= command_read_counter + 1'b1;
        else begin
            if (command_fifo_read_req) begin
                command_read_counter <= 3'h1;
            end
            else if (clear_command_read_counter) begin
                command_read_counter <= 3'h0;
            end
        end
    end
end

assign data_ready = &data_read_counter;
always_ff @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        data_read_counter <= 3'h0;
    end 
    else begin
        if (data_read_counter > 0 && data_read_counter < 3'h7)
            data_read_counter <= data_read_counter + 1'b1;
        else begin
            if (write_fifo_read_req) begin
                data_read_counter <= 3'h1;
            end
            else if (clear_data_read_counter) begin
                data_read_counter <= 3'h0;
            end
        end
    end
end

always_ff @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        clr_read_fifo_dyn_addr_par_dly <= 1'b0;
    end 
    else begin
        clr_read_fifo_dyn_addr_par_dly <= clr_read_fifo_dyn_addr_par;
    end
end

assign clr_read_fifo_dyn_addr_par_pulse = clr_read_fifo_dyn_addr_par & ~clr_read_fifo_dyn_addr_par_dly;

always_ff @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        status_ff <= STATUS_IDLE;
    end 
    else begin
        status_ff <= status;
        new_status <= status_ff != status;
    end
end

always_ff @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        state <= IDLE;
        unique_id_read_counter <= 3'b0;
        status_reg <= ERROR_CODE_SUCCESS;
        address_reg <= 7'h0;
        num_bytes_reg <= 9'h0;
        toc_reg <= 1'b0;
        rnw_reg <= 1'b0;
        data_tx_reg <= 32'h0;
        write_shift_counter <= 2'b0;
        data_rx_reg <= 32'h0;
        write_shift_counter <= 2'b0;
        expected_num_bytes <= 9'b0;
        read_shift_counter  <= 2'b0;
        ibi_reg <= 32'h0;
        ibi_message_ready <= 1'b0;
        dyanmic_addr_assignemnt_reg <= 7'h08;
        dynamic_addr_nack_counter <= 2'b0;
        rd_data_valid_dly <= 1'b0;
    end 
    else begin
        state <= state_next;
        unique_id_read_counter <= unique_id_counter_next;
        status_reg <= status_reg_next;
        address_reg <= address_reg_next;
        num_bytes_reg <= num_bytes_reg_next;
        toc_reg <= toc_reg_next;
        rnw_reg <= rnw_reg_next;
        data_tx_reg <= data_tx_reg_next;
        write_shift_counter <= write_shift_counter_next;
        data_rx_reg <= data_rx_reg_next;
        read_shift_counter <= read_shift_counter_next;
        expected_num_bytes <= expected_num_bytes_next;
        ibi_reg <= ibi_reg_next;
        ibi_message_ready <= ibi_message_ready_next;
        dyanmic_addr_assignemnt_reg <= dyanmic_addr_assignemnt_reg_next;
        dynamic_addr_nack_counter <= dynamic_addr_nack_counter_next;
        rd_data_valid_dly <= rd_data_valid;
    end
end

assign par_bit = ~^dyanmic_addr_assignemnt_reg;

always_comb begin
    state_next = state;
    request = REQUEST_IDLE;
    wr_data = 8'h0;
    unique_id_counter_next = unique_id_read_counter;
    status_reg_next = status_reg;
    command_fifo_read_req = 1'b0;
    clear_command_read_counter = 1'b0;
    clear_data_read_counter = 1'b0;
    address_reg_next = address_reg;
    num_bytes_reg_next = num_bytes_reg;
    toc_reg_next = toc_reg;
    rnw_reg_next = rnw_reg;
    data_tx_reg_next = data_tx_reg;
    write_fifo_read_req = 0;
    write_shift_counter_next = write_shift_counter;
    data_rx_reg_next = data_rx_reg;
    read_shift_counter_next = read_shift_counter;
    read_fifo_write_req = 1'b0;
    read_fifo_write_data = 32'h0;
    expected_num_bytes_next = expected_num_bytes;
    response_fifo_write_req = 1'b0;
    response_fifo_write_data = 32'h0;
    ibi_reg_next = ibi_reg;
    ibi_message_ready_next = ibi_message_ready;
    dyanmic_addr_assignemnt_reg_next = dyanmic_addr_assignemnt_reg;
    dynamic_addr_nack_counter_next = dynamic_addr_nack_counter;
    clr_read_fifo_dyn_addr_par = 1'b0;
    
    case (state)
        IDLE : begin
            if (!command_fifo_empty && command_read_counter == 3'h0) begin
                command_fifo_read_req = 1'b1;
            end
            if (command_ready) begin
                clear_command_read_counter = 1'b1;
                state_next = START;
                status_reg_next = ERROR_CODE_SUCCESS;
            end
             
            if (new_status && status_ff == STATUS_IBI_START) begin
                state_next = IBI_READ_ADDR;
            end
            
        end

        START : begin
            address_reg_next = address;
            num_bytes_reg_next = num_bytes;
            toc_reg_next = toc;
            rnw_reg_next = rnw;
            expected_num_bytes_next = num_bytes;
            request = REQUEST_START;
            dynamic_addr_nack_counter_next = 2'b0;
            if (new_status ) begin
                if( status_ff == STATUS_SINGLE_START || status_ff == STATUS_REPEATED_START) begin
                    if (command_mode == STANDARD_TRANSFER_MODE) begin
                        state_next = WRITE_ADDRESS ;
                    end else if (command_mode == ADDR_ASSIGNMENT_MODE) begin
                        state_next = WRITE_RESERVE_BYTE;
                        expected_num_bytes_next = 6'h0;
                        num_bytes_reg_next = 6'h0;
                    end else begin
                        state_next = PUSH_STATUS;
                        status_reg_next = ERROR_CODE_FRAME_ERR;
                    end
                end     
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end

                // request the first word of data if it is a write & not ENTDAA sequence (as ENTDAA sequence will not need any writedata from write_fifo)
                // push an error if the read fifo is full or the write fifo is empty
                if (~rnw & command_mode != ADDR_ASSIGNMENT_MODE) begin
                    if (num_bytes > 0 & !write_fifo_empty) begin
                        write_fifo_read_req = 1'b1;
                    end 
                    else begin
                        if (write_fifo_empty) begin
                            state_next = PUSH_STATUS;
                            status_reg_next = ERROR_CODE_OVER_OR_UNDER_FLOW;
                        end
                    end
                end else 
                if (rnw & command_mode != ADDR_ASSIGNMENT_MODE) begin
                    if (read_fifo_full) begin
                            state_next = PUSH_STATUS;
                            status_reg_next = ERROR_CODE_OVER_OR_UNDER_FLOW;
                    end
                end
            end
        end

        WRITE_RESERVE_BYTE : begin
            wr_data = {RESERVE_BYTE, 1'b0};
            request = REQUEST_ADDRESS;

            if (new_status) begin
                if(status_ff == STATUS_ADDRESS_LAST) begin
                    state_next = ACK_AFTER_RESERVE_BYTE;
                end
                // Unexpected status abort
                else if (status_ff != STATUS_ADDRESS) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        ACK_AFTER_RESERVE_BYTE : begin
            wr_data = ENTDAA_CODE;
            request = REQUEST_DATA;
            if (new_status) begin
                if (status_ff == STATUS_ACK) begin
                    state_next = WRITE_CCC;
                end
                else if (new_status && status_ff == STATUS_NACK) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_NACK;
                end
                else begin 
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        WRITE_CCC : begin
            wr_data = ENTDAA_CODE;
            request = REQUEST_DATA;
            if (new_status ) begin
                if(status_ff == STATUS_DATA_LAST) begin
                    state_next = START_DYNAMIC_ADDR;
                end
                else if (status_ff != STATUS_DATA) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end

        end

        WRITE_ADDRESS : begin
            request = REQUEST_ADDRESS;
            wr_data = {address_reg, rnw_reg};

            if (new_status) begin
                if(status_ff == STATUS_ADDRESS_LAST) begin
                    if (rnw_reg == 1'b1) begin
                        state_next = ACK_HANDOFF;
                    end
                    else begin 
                        state_next = ACK_NOHANDOFF;
                    end
                end
                if (status_ff != STATUS_ADDRESS && status_ff != STATUS_ADDRESS_LAST) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        START_DYNAMIC_ADDR : begin
            wr_data = ENTDAA_CODE;
            request = REQUEST_TRANSITION_PARITY;
            if (new_status) begin
                if (status_ff == STATUS_TRANSITION_PARITY) begin
                    state_next = REPEAT_START_DYNAMIC_ADDR;
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        REPEAT_START_DYNAMIC_ADDR : begin
            wr_data = ENTDAA_CODE;
            request = REQUEST_START;
            if (new_status) begin
                if(status_ff == STATUS_REPEATED_START) begin
                    state_next = WRITE_RESERVE_BYTE_DYNAMIC_ADDR;
                end 
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        WRITE_RESERVE_BYTE_DYNAMIC_ADDR : begin
            request = REQUEST_ADDRESS;
            wr_data = {RESERVE_BYTE, 1'b1};

            if (new_status) begin
                if(status_ff == STATUS_ADDRESS_LAST) begin
                    state_next = ACK_AFTER_RESERVE_BYTE_DYNAMIC_ADDR;
                end
                // Unexpected status abort
                else if (status_ff != STATUS_ADDRESS) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end
        
        ACK_AFTER_RESERVE_BYTE_DYNAMIC_ADDR : begin 
            if (new_status) begin
                if (status_ff == STATUS_ACK & dynamic_addr_nack_counter != 2'h2) begin
                    state_next = READ_UNIQUE_ID;
                    data_rx_reg_next = 32'h0;
                    read_shift_counter_next = 2'h0;
                    unique_id_counter_next = 3'b0;
                    request = REQUEST_ADDRESS;
                end 
                // transaction is expected to end in a nack once all devices get an address
                else if (status_ff == STATUS_NACK & dynamic_addr_nack_counter == 2'h0) begin
                    state_next      = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_SUCCESS;
                    request         = REQUEST_STOP;
                //if the i3c host receives a NACK and there was a NACK detected on the dynamic address assignment parity bit, this indicates the i3c slave does not acknowledge the parity bit of the assigned dynamic address is valid.
                end else if (status_ff == STATUS_NACK & dynamic_addr_nack_counter != 2'h0) begin 
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_DYN_ADDR_PAR_NACK;
                    request = REQUEST_ADDRESS;
                //if the i3c host receives a ACK and there was already 2 attempts to assign dynamic address but receive NACK on parity bit, this indicates the i3c slave does not acknowledge the parity bit of the assigned dynamic address is valid.
                end else if (status_ff == STATUS_ACK & dynamic_addr_nack_counter == 2'h2) begin 
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_DYN_ADDR_PAR_NACK;
                    request = REQUEST_ADDRESS;
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                    request = REQUEST_ADDRESS;
                end
            end 
            else
            begin
              state_next = ACK_AFTER_RESERVE_BYTE_DYNAMIC_ADDR;
              request    = REQUEST_STATE_CAP_ACK_NACK;
            end
        end

        READ_UNIQUE_ID : begin
            if (rd_data_valid) begin 
                data_rx_reg_next = {data_rx_reg[24:0], rd_data};
            end
            
            if (rd_data_valid_dly) begin 
                unique_id_counter_next = unique_id_read_counter + 1'b1;
            end

            if (unique_id_counter_next == 3'h4 && unique_id_read_counter == 3'h3) begin 
                read_fifo_write_req = 1'b1;
                read_fifo_write_data = data_rx_reg_next;
                expected_num_bytes_next = expected_num_bytes + 9'h4;
            end
            
            if (new_status && status_ff == STATUS_DATA_LAST) begin

                if (unique_id_read_counter == 3'h7) begin
                    state_next = WRITE_DYNAMIC_ADDR;
                    request = REQUEST_DATA;
                    wr_data = {dyanmic_addr_assignemnt_reg, par_bit};
                    data_rx_reg_next = 32'h0;
                    read_fifo_write_req = 1'b1;
                    read_fifo_write_data = {data_rx_reg[24:0], rd_data};
                    expected_num_bytes_next = expected_num_bytes + 9'h4;
                end

                if (read_fifo_full) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_OVER_OR_UNDER_FLOW;
                end

            end
        end

        WRITE_DYNAMIC_ADDR : begin
            request = REQUEST_DATA;
            wr_data = {dyanmic_addr_assignemnt_reg, par_bit};
            if (new_status && status_ff == STATUS_DATA_LAST) begin
                state_next = WRITE_PARITY_DYNAMIC_ADDR;
                dyanmic_addr_assignemnt_reg_next = dyanmic_addr_assignemnt_reg + 1'b1;
                read_fifo_write_req = 1'b1;
                // write the assigned address to the write fifo after the 8 bytes of the unique ID
                // it is only 7 bits so its pushed up to bits [31:25] to keep with the 
                read_fifo_write_data = dyanmic_addr_assignemnt_reg << 25;

                expected_num_bytes_next = expected_num_bytes + 9'h4;
            end
        end

        WRITE_PARITY_DYNAMIC_ADDR : begin

            if (new_status && (status_ff == STATUS_ACK || status_ff == STATUS_NACK)) begin
                if (status_ff == STATUS_NACK) begin 
                    dynamic_addr_nack_counter_next = dynamic_addr_nack_counter + 1'b1;
                    clr_read_fifo_dyn_addr_par = 1'b1;
                end else if (status_ff == STATUS_ACK) begin 
                    dynamic_addr_nack_counter_next = 2'b0;
                end
                state_next = REPEAT_START_DYNAMIC_ADDR;
                request    = REQUEST_START;
            end
            else
            begin
                request    = REQUEST_DATA;
            end
        end


        // Read byte procedure
        ACK_HANDOFF : begin
            if (new_status ) begin
                if (status_ff == STATUS_ACK) begin
                    state_next = READ;
                    read_shift_counter_next = 2'b0;
                    // fetch the next command 
                    // we fetch earlier that in a write because
                    // the slave can cut a read transaction short so num_bytes can't be used to estimate the end of the transaction
                    if (~toc && !command_fifo_empty) begin
                        command_fifo_read_req = 1'b1;
                    end
                end
                else if (status_ff == STATUS_NACK) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_NACK;
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        READ : begin
            if (new_status) begin
                if (status_ff == STATUS_DATA_LAST) begin
                    data_rx_reg_next = {data_rx_reg[24:0], rd_data};
                    state_next = CONTINUE_READ;
                    read_shift_counter_next = read_shift_counter + 1'b1;
                    num_bytes_reg_next = num_bytes_reg - 1'b1;

                end
                else if (status_ff != STATUS_DATA) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                    // make sure we write whatever data is still in the rx shifter if we abort
                    if (read_shift_counter != 2'b00) begin
                        read_fifo_write_req = 1'b1; 
                        data_rx_reg_next = 32'h0;
                        if (read_shift_counter == 2'b01) begin
                            read_fifo_write_data = data_rx_reg << 24;
                        end else if (read_shift_counter == 2'b10) begin
                            read_fifo_write_data = data_rx_reg << 16;
                        end else begin
                            read_fifo_write_data = data_rx_reg << 8;
                        end 
                    end
                end
            end
        end

        CONTINUE_READ : begin   
            if (new_status) begin
                if (read_shift_counter == 2'b00) begin
                    data_rx_reg_next = 32'h0;
                    read_fifo_write_data = data_rx_reg;
                    read_fifo_write_req = 1'b1;     
                end 
                // write a parital word
                else if ((status_ff == STATUS_TRANSITION_CONTINUE && num_bytes_reg == 9'h0) 
                    || status_ff == STATUS_TRANSITION_END) begin
                    read_fifo_write_req = 1'b1; 
                    data_rx_reg_next = 32'h0;
                    if (read_shift_counter == 2'b01) begin
                        read_fifo_write_data = data_rx_reg << 24;
                    end else if (read_shift_counter == 2'b10) begin
                        read_fifo_write_data = data_rx_reg << 16;
                    end else begin
                        read_fifo_write_data = data_rx_reg << 8;
                    end                 
                end
                if (status_ff == STATUS_TRANSITION_CONTINUE) begin
                    if (num_bytes_reg == 9'h0) begin
                        state_next = PUSH_STATUS;
                        if (pec_enable_int && (pec_code != 8'd0)) begin
                            status_reg_next = ERROR_CODE_PEC_ERR;
                        end
                    end else begin
                        state_next = READ;
                    end
                end
                else if (status_ff == STATUS_TRANSITION_END) begin
                    state_next = PUSH_STATUS;
                    if (pec_enable_int && (pec_code != 8'd0)) begin
                        status_reg_next = ERROR_CODE_PEC_ERR;
                    end
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        // write byte procedure
        ACK_NOHANDOFF : begin

            // drive data in ack handoff state because phy will register the next flag right as
            // the ACK/NACK comes in
            wr_data = write_fifo_read_data[31:24];                  
                    
            if (new_status) begin
                if(status_ff == STATUS_ACK) begin
                    state_next = WRITE;
                    request    = REQUEST_DATA;
                    write_shift_counter_next = 2'b0;
                    data_tx_reg_next = write_fifo_read_data;
                    assert(data_ready)
                        else $display("data is not ready at time %t", $time);
                    // fetch the next command if we are getting close to finishing this one
                    // This coniditon only happens in the specific case where 1 byte of data is requested to be written/read
                    if (~toc && num_bytes_reg ==9'b1) begin
                        command_fifo_read_req = 1'b1;
                    end
                end 
                else if (status_ff == STATUS_NACK) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_NACK;
                    request    = REQUEST_STATE_CAP_ACK_NACK;
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                    request    = REQUEST_STATE_CAP_ACK_NACK;
                end
            end
            else
            begin
              request    = REQUEST_STATE_CAP_ACK_NACK;
            end
        end

        WRITE : begin
            wr_data = data_tx_reg[31:24];
            request = REQUEST_DATA;

            if (new_status) begin
                if(status_ff == STATUS_DATA_LAST) begin
                    state_next = CONTINUE_WRITE;
                    num_bytes_reg_next = num_bytes_reg - 1'b1;
                    write_shift_counter_next = write_shift_counter + 1'b1;
                    data_tx_reg_next = {data_tx_reg[23:0], 8'h0};
                    if (write_shift_counter == 2'b11) begin
                        assert(data_ready)
                            else $display("data is not ready at time %t", $time);
                        data_tx_reg_next = write_fifo_read_data;
                    end
                end
                else if (status_ff != STATUS_DATA) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end

        end

        CONTINUE_WRITE : begin
            request = REQUEST_TRANSITION_PARITY;
            if (new_status) begin
                if (status_ff == STATUS_TRANSITION_PARITY) begin
                    if (num_bytes_reg == 0) begin
                        if (pec_enable_int) begin
                            state_next = WRITE_PEC;
                        end
                        else begin
                            state_next = PUSH_STATUS;
                        end
                    end
                    else begin
                        // fetch the next word when there is one byte left to write out
                        // we still have plenty of time to let the read from the fifo finish
                        if (num_bytes_reg > 9'h1 && write_shift_counter == 2'b11)
                            write_fifo_read_req = 1'b1;
                        // fetch the next command if we are getting close to finishing this one
                        if (~toc && num_bytes_reg ==9'b1) begin
                            command_fifo_read_req = 1'b1;
                        end
                        state_next = WRITE;
                    end
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end
        
        WRITE_PEC : begin
            wr_data = pec_code;
            request = REQUEST_DATA;

            if (new_status) begin
                if(status_ff == STATUS_DATA_LAST) begin
                    state_next = CONTINUE_WRITE_PEC;
                end
                else if (status_ff != STATUS_DATA) begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end

        end

        CONTINUE_WRITE_PEC : begin
            request = REQUEST_TRANSITION_PARITY;
            if (new_status) begin
                if (status_ff == STATUS_TRANSITION_PARITY) begin
                    state_next = PUSH_STATUS;
                end
                else begin
                    state_next = PUSH_STATUS;
                    status_reg_next = ERROR_CODE_ABORTED;
                end
            end
        end

        SET_STOP : begin
            request = REQUEST_STOP;
            if (status == STATUS_STOP) begin
                state_next = IDLE;
                request = REQUEST_IDLE;
            end
        end

        IBI_READ_ADDR : begin
            if (new_status) begin
                if (status_ff == STATUS_IBI_ADDRESS_LAST) begin                 
                    state_next = IBI_SEND_ACK;                    
                end
                else if (status_ff != STATUS_IBI_ADDRESS) begin
                    state_next = SET_STOP;
                end
            end
        end

        IBI_SEND_ACK : begin
            request = REQUEST_ACK;
            if (new_status) begin
                if (status_ff == STATUS_ACK) begin
                    ibi_reg_next = {1'b0, rd_data[7:1], 24'h0};
                    state_next = IBI_READ_BYTE;
                    read_shift_counter_next = 2'h0;
                    data_rx_reg_next = 32'h0;
                end
                else begin
                    state_next = SET_STOP;
                end
            end
        end
        IBI_READ_BYTE : begin
            if (new_status) begin
                if (status_ff == STATUS_DATA_LAST) begin
                    data_rx_reg_next = {data_rx_reg[23:0], rd_data};
                    state_next = IBI_READ_CONTINUE;
                end
                else if (status_ff != STATUS_DATA) begin
                    // something went wrong
                    state_next = SET_STOP;
                end
            end
        end
        IBI_READ_CONTINUE : begin
            if (new_status) begin
                if (status_ff == STATUS_TRANSITION_CONTINUE) begin
                    // the IBI register is only 4 bytes and 1 is for the address so we force a stop after 3 bytes
                    if (read_shift_counter == 2'h2) begin
                        ibi_reg_next[23:0] = data_rx_reg[23:0];
                        ibi_message_ready_next = 1'b1;
                        state_next = SET_STOP;
                        request = REQUEST_STOP;
                    end
                    else begin
                        read_shift_counter_next = read_shift_counter + 1'b1;
                        state_next = IBI_READ_BYTE;
                    end
                end
                else if (status_ff == STATUS_TRANSITION_END) begin
                    ibi_message_ready_next = 1'b1;
                    state_next = SET_STOP;
                    request = REQUEST_STOP;
                    case (read_shift_counter)
                        2'h0 : ibi_reg_next[23:0] = {data_rx_reg[7:0], 16'h0};
                        2'h1 : ibi_reg_next[23:0] = {data_rx_reg[15:0], 8'h0};
                        2'h2 : ibi_reg_next[23:0] = data_rx_reg[23:0];
                    endcase 
                end
            end

        end

        IBI_SEND_NACK : begin
            request = REQUEST_NACK;
            if (new_status) begin
                // we expect the status to be STATUS_NACK
                // but we send a stop no matter what so skip the check
                state_next = SET_STOP;
            end
        end

        PUSH_STATUS : begin
            response_fifo_write_req = 1'b1;
            response_fifo_write_data[31:28] = status_reg;
            response_fifo_write_data[27:16] = 12'h0;
            response_fifo_write_data[15:0] = expected_num_bytes - num_bytes_reg;

            if (toc_reg) begin
                state_next = SET_STOP;
                request = REQUEST_STOP;
            end else begin
                if (command_ready) begin
                    state_next = START;
                    clear_command_read_counter = 1'b1;
                end
            end


        end
        default : state_next = IDLE;
    endcase

    // put outside of case statment becuase many states can cause this situation
    if (command_fifo_read_req & command_fifo_empty || write_fifo_read_req & write_fifo_empty) begin
        state_next = IDLE;
        status_reg_next = ERROR_CODE_OVER_OR_UNDER_FLOW;
    end

    if (ibi_message_clear) begin
        ibi_message_ready_next = 1'b0;
    end

end


endmodule
